package com.zhangfb95.crosschain.plugin.container.java.fabric2.service.impl;

import com.google.protobuf.InvalidProtocolBufferException;
import com.zhangfb95.crosschain.infra.exception.BusinessException;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.config.properties.FabricProperties;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.data.mapper.Fabric2BlockEventMapper;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.data.mapper.Fabric2TxEventMapper;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.enums.ChaincodeEventEnum;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.enums.EventHandleStatusEnum;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.mapper.entity.Fabric2BlockEvent;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.mapper.entity.Fabric2TxEvent;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.service.dto.ChaincodeEventRegisterInfo;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.service.ChaincodeEventService;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.service.FabricService;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.service.chaincodeevent.ChaincodeEventHandler;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.hyperledger.fabric.sdk.BlockInfo;
import org.hyperledger.fabric.sdk.ChaincodeEvent;
import org.hyperledger.fabric.sdk.Channel;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Pattern;

/**
 * @author zhangfubing
 * @since 2024/1/9
 */
@Slf4j
@Service
public class ChaincodeEventServiceImpl implements ChaincodeEventService {

    private final Map<String, ChaincodeEventHandler> chaincodeEventHandlerMap = new HashMap<>();
    private final ConcurrentMap<String, ChaincodeEventRegisterInfo> chaincodeEventRegisterInfoMap = new ConcurrentHashMap<>();

    @Autowired
    private FabricProperties fabricProperties;
    @Autowired
    private FabricService fabricService;
    @Autowired
    private List<ChaincodeEventHandler> chaincodeEventHandlers;
    @Autowired
    private Fabric2BlockEventMapper fabric2BlockEventMapper;
    @Autowired
    private Fabric2TxEventMapper fabric2TxEventMapper;

    @PostConstruct
    public void init() {
        for (ChaincodeEventHandler chaincodeEventHandler : chaincodeEventHandlers) {
            chaincodeEventHandlerMap.put(chaincodeEventHandler.event().getName(), chaincodeEventHandler);
        }
    }

    @PreDestroy
    public void destroy() {
        chaincodeEventRegisterInfoMap.forEach((key, chaincodeEventRegisterInfo) -> {
            String handle = chaincodeEventRegisterInfo.getHandle();
            try {
                chaincodeEventRegisterInfo.getChannel().unregisterChaincodeEventListener(handle);
            } catch (InvalidArgumentException e) {
                throw new BusinessException(e);
            }
        });
    }

    /**
     * 注册事件
     */
    @Override
    public void registerEvent() {
        // 获取通道和链码信息
        Map<String, Map<String, String>> channel2chainCode2VersionMap =
                fabricService.queryInstalledChainCodes(null);

        // 遍历通道和链码列表，以便注册事件
        channel2chainCode2VersionMap.forEach((channelName, chainCode2VersionMap) -> {
            List<String> includeChannelNames = fabricProperties.getChaincodeEvent().getIncludeChannelNames();
            if (!includeChannelNames.contains("*") && !includeChannelNames.contains(channelName)) {
                return;
            }

            chainCode2VersionMap.forEach((chainCodeName, version) -> {
                try {
                    Channel channel = fabricService.buildChannel(channelName);
                    for (ChaincodeEventEnum chaincodeEventEnum : ChaincodeEventEnum.values()) {
                        // 事件名称
                        String eventName = chaincodeEventEnum.getName();
                        // 是否已经注册过
                        String eventKey = channelName + ":" + chainCodeName + ":" + eventName;
                        if (chaincodeEventRegisterInfoMap.containsKey(eventKey)) {
                            continue;
                        }

                        // 注册事件
                        String registeredHandle = channel.registerChaincodeEventListener(
                                Pattern.compile("^" + chainCodeName + "$"),
                                Pattern.compile("^" + eventName + "$"),
                                (handle, blockEvent, chaincodeEvent) -> {
                                    // 获取通道id
                                    String eventChannelId;
                                    try {
                                        eventChannelId = blockEvent.getChannelId();
                                    } catch (InvalidProtocolBufferException e) {
                                        throw new BusinessException(e);
                                    }

                                    // 拼装事件对象
                                    Fabric2TxEvent fabric2TxEvent = new Fabric2TxEvent()
                                            .setChainName(fabricProperties.getChainName())
                                            .setChannelName(eventChannelId)
                                            .setChainCodeName(chaincodeEvent.getChaincodeId())
                                            .setBlockNumber(blockEvent.getBlockNumber())
                                            .setTxId(chaincodeEvent.getTxId())
                                            .setEventName(chaincodeEvent.getEventName())
                                            .setEventPayload(new String(chaincodeEvent.getPayload(), StandardCharsets.UTF_8))
                                            .setHandleStatus(EventHandleStatusEnum.WAIT.getCode())
                                            .setHandleCount(0);

                                    // 处理事件
                                    handle(chaincodeEvent.getEventName(), fabric2TxEvent);
                                });
                        // 设置事件信息
                        chaincodeEventRegisterInfoMap.put(eventKey, new ChaincodeEventRegisterInfo()
                                .setRegisterTimestamp(System.currentTimeMillis())
                                .setEventName(eventName)
                                .setChannelName(channelName)
                                .setChaincodeName(chainCodeName)
                                .setChannel(channel)
                                .setHandle(registeredHandle));
                    }
                } catch (InvalidArgumentException e) {
                    throw new BusinessException(e);
                }
            });
        });
    }

    @Override
    public void startSync() {
        // 获取链名称
        String chainName = fabricProperties.getChainName();

        // 查询通道列表
        Set<String> channelNames;
        try {
            channelNames = fabricService.queryChannels();
        } catch (Exception e) {
            log.info("queryChannels err, {}", e.getMessage(), e);
            return;
        }

        for (String channelName : channelNames) {
            // 需要保证区块信息是存在的
            Fabric2BlockEvent fabric2BlockEvent = fabric2BlockEventMapper.getBlockEvent(chainName, channelName);
            if (fabric2BlockEvent == null) {
                fabric2BlockEventMapper.insert(chainName, channelName);
                return;
            }

            // 获取区块高度
            long height = fabricService.getHeight(channelName);

            // 将区块编号更新为通道的最新区块编号
            if (fabric2BlockEvent.getBlockNumber() < 1L) {
                long maxBlockNumber = height - 1L;
                fabric2BlockEventMapper.setBlockNumber(chainName, channelName, maxBlockNumber);
                return;
            }

            for (long blockNumber = fabric2BlockEvent.getBlockNumber() + 1; blockNumber < height; blockNumber++) {
                List<Fabric2TxEvent> fabric2TxEvents = getFabric2TxEvents(chainName, channelName, blockNumber);
                for (Fabric2TxEvent fabric2TxEvent : fabric2TxEvents) {
                    fabric2TxEventMapper.insertIgnore(fabric2TxEvent);
                }
                fabric2BlockEventMapper.setBlockNumber(chainName, channelName, blockNumber);
            }
        }
    }

    @Override
    public void startRetry() {
        // 查询通道列表
        Set<String> channelNames;
        try {
            channelNames = fabricService.queryChannels();
        } catch (Exception e) {
            log.info("queryChannels err, {}", e.getMessage(), e);
            return;
        }

        for (String channelName : channelNames) {
            long lastId = -1L;
            do {
                List<Fabric2TxEvent> fabric2TxEvents =
                        fabric2TxEventMapper.getWaitOrFailList(fabricProperties.getChainName(), channelName, lastId);
                if (CollectionUtils.isEmpty(fabric2TxEvents)) {
                    break;
                }

                for (Fabric2TxEvent fabric2TxEvent : fabric2TxEvents) {
                    handle(fabric2TxEvent.getEventName(), fabric2TxEvent);
                    lastId = fabric2TxEvent.getId();
                }
            } while (true);
        }
    }

    private void handle(String eventName, Fabric2TxEvent fabric2TxEvent) {
        ChaincodeEventHandler chaincodeEventHandler = chaincodeEventHandlerMap.get(eventName);
        if (chaincodeEventHandler == null) {
            String err = "chaincodeEventHandler not find, eventName: " + eventName;
            log.info(err);
            throw new BusinessException(err);
        }
        chaincodeEventHandler.handle(fabric2TxEvent);
    }

    private List<Fabric2TxEvent> getFabric2TxEvents(String chainName, String channelName, long blockNumber) {
        List<Fabric2TxEvent> fabric2TxEvents = new ArrayList<>();

        BlockInfo blockInfo = fabricService.queryBlockByNumber(channelName, blockNumber);
        for (BlockInfo.EnvelopeInfo envelopeInfo : blockInfo.getEnvelopeInfos()) {
            // 只处理交易信息
            if (!(envelopeInfo instanceof BlockInfo.TransactionEnvelopeInfo)) {
                continue;
            }

            // 转换为交易信息
            BlockInfo.TransactionEnvelopeInfo transactionEnvelopeInfo =
                    (BlockInfo.TransactionEnvelopeInfo) envelopeInfo;

            // 跳过无效的交易
            if (!transactionEnvelopeInfo.isValid()) {
                continue;
            }

            for (BlockInfo.TransactionEnvelopeInfo.TransactionActionInfo transactionActionInfo :
                    transactionEnvelopeInfo.getTransactionActionInfos()) {
                ChaincodeEvent event = transactionActionInfo.getEvent();
                if (event != null && chaincodeEventHandlerMap.containsKey(event.getEventName())) {
                    Fabric2TxEvent fabric2TxEvent = new Fabric2TxEvent();
                    fabric2TxEvent.setChainName(chainName);
                    fabric2TxEvent.setChannelName(channelName);
                    fabric2TxEvent.setChainCodeName(transactionActionInfo.getChaincodeIDName());
                    fabric2TxEvent.setBlockNumber(blockNumber);
                    fabric2TxEvent.setTxId(transactionEnvelopeInfo.getTransactionID());
                    fabric2TxEvent.setEventName(event.getEventName());
                    fabric2TxEvent.setEventPayload(new String(event.getPayload(), StandardCharsets.UTF_8));
                    fabric2TxEvent.setHandleStatus(EventHandleStatusEnum.WAIT.getCode());
                    fabric2TxEvent.setHandleCount(0);
                    fabric2TxEvents.add(fabric2TxEvent);
                }
            }
        }

        return fabric2TxEvents;
    }
}
